package scfg;

import java.util.*;

import scfg.output.Display;
import scfg.output.database.DBAccessionNumberPair;
import scfg.utils.Compare;

public class AlignedSequence {

	private List<DBAccessionNumberPair> accessionNumber;
	private String name;
	private String rnaClass;
	private String rnaSubClass;
	private String sequence;
	private String alignment;
	private HashMap<String, RnaAlgoPrediction> structure;
	private Queue<RnaAlgoPrediction> deleted;
	private Boolean duplicate;
	private Boolean inAlignment;
	private Double dense;
	private Double denseComp;
	private Double percIdentity;
	private Integer alnseqid;
	private Integer seqid;
	private Integer filid;
	private Integer infoid;

	public List<DBAccessionNumberPair> getAccessionNumber() {
		return accessionNumber;
	}
	
	public void addAccessionNumber(String accessionNumber) {
		if (!this.accessionNumber.contains(accessionNumber))
			this.accessionNumber.add(new DBAccessionNumberPair(accessionNumber));
	}

	public void setAccessionNumber(List<DBAccessionNumberPair> accessionNumber) {
		this.accessionNumber = accessionNumber;
	}

	public String getName() {
		return name;
	}

	public void setName(String name) {
		this.name = name;
	}

	public String getRnaClass() {
		return rnaClass;
	}

	public void setRnaClass(String rnaClass) {
		this.rnaClass = rnaClass;
	}

	public String getRnaSubClass() {
		return rnaSubClass;
	}

	public void setRnaSubClass(String rnaSubClass) {
		this.rnaSubClass = rnaSubClass;
	}

	public String getSequence() {
		return sequence;
	}

	public void setSequence(String sequence) {
		this.sequence = sequence.toUpperCase();
	}

	public int getSequenceLength() {
		return this.sequence == null ? 0 : this.sequence.length();
	}

	public String getAlignment() {
		return alignment;
	}

	public void setAlignment(String alignment) {
		this.alignment = alignment.toUpperCase();
	}

	public RnaAlgoPrediction getStructure(String algo) {
		return structure.get(algo);
	}
	
	public Collection<RnaAlgoPrediction> getAllStructures() {
		return structure.values();
	}
	
	public Queue<RnaAlgoPrediction> getDeletedStructures() {
		return deleted;
	}

	public boolean addStructure(RnaAlgoPrediction structure) {
		boolean exists = this.structure.get(structure.getAlgo()) != null;
		if (!exists)
			this.structure.put(structure.getAlgo(), structure);
		return exists;
	}

	public boolean updateStructure(RnaAlgoPrediction structure) {
		boolean exists = this.structure.get(structure.getAlgo()) != null;
		if (exists)
			this.structure.put(structure.getAlgo(), structure);
		return exists;
	}

	public boolean removeStructure(String algo) {
		boolean exists = this.structure.get(algo) != null;
		if (exists) {
			deleted.add(structure.get(algo));
			this.structure.remove(algo);
		}
		return exists;
	}
	
	public double getFMeasure() {
		if (this.getStructure("refrence") != null && this.getStructure("mfe_gt") != null)
			return Compare.getFMeasure(this.getStructure("refrence").getStructure(), this.getStructure("mfe_gt").getStructure());
		System.out.println("failed");
		return -1;
	}
	
	public String getFMeasureString() {
		return "" + getFMeasure();
	}

	public void resetStructure() {
		this.structure = new HashMap<String, RnaAlgoPrediction>();
	}

	public Boolean getAmbiguous() {
		return this.sequence == null ? null : !sequence.matches("[ACGU]+");
	}

	public String getAmbiguousString() {
		return this.sequence == null ? null : this.getAmbiguous() ? "1" : "0";
	}

	public Boolean getDuplicate() {
		return duplicate;
	}
	
	public String getDuplicateString() {
		return duplicate == null ? "NULL" : duplicate ? "1" : "0";
	}

	public void setDuplicate(Boolean duplicate) {
		this.duplicate = duplicate;
	}

	public Boolean getInAlignment() {
		return inAlignment;
	}
	
	public String getInAlignmentString() {
		return inAlignment == null ? "NULL" : inAlignment ? "1" : "0";
	}

	public void setInAlignment(Boolean inalignment) {
		this.inAlignment = inalignment;
	}

	public Double getDense() {
		if (dense == null)
			return (double)-1;
		return dense;
	}
	
	public String getDenseString() {
		return dense == null ? "NULL" : dense.toString();
	}

	public void setDense(Double dense) {
		this.dense = dense;
	}

	public Double getDenseComp() {
		if (denseComp == null)
			return (double)-1;
		return denseComp;
	}
	
	public String getDenseCompString() {
		return denseComp == null ? "NULL" : denseComp.toString();
	}

	public void setDenseComp(Double denseComp) {
		this.denseComp = denseComp;
	}

	public Double getPercIden() {
		return percIdentity;
	}
	
	public String getPercIdenString() {
		return percIdentity == null ? "NULL" : percIdentity.toString();
	}

	public void setPercIden(Double percIdentity) {
		this.percIdentity = percIdentity;
	}

	public Integer getAlnseqid() {
		return alnseqid;
	}

	public void setAlnseqid(Integer alnseqid) {
		if (this.alnseqid == null)
			this.alnseqid = alnseqid;
	}

	public Integer getSeqid() {
		return seqid;
	}

	public void setSeqid(Integer seqid) {
		if (this.seqid == null)
			this.seqid = seqid;
	}

	public Integer getFilid() {
		return filid;
	}

	public void setFilid(Integer filid) {
		if (this.filid == null)
			this.filid = filid;
	}

	public Integer getInfoid() {
		return infoid;
	}

	public void setInfoid(Integer infoid) {
		if (this.infoid == null)
			this.infoid = infoid;
	}

	public AlignedSequence() {
		this(new HashMap<String, String>(), null, null);
	}

	private Double getDouble(String num) {
		try {
			return Double.parseDouble(num);
		} catch (Exception e) {
			return null;
		}
	}

	private Integer getInteger(String num) {
		try {
			return Integer.parseInt(num);
		} catch (Exception e) {
			return null;
		}
	}

	public AlignedSequence(HashMap<String, String> vals,
			HashMap<String, RnaAlgoPrediction> structures,
			List<DBAccessionNumberPair> accessionNums) {
		if (vals != null) {
			this.accessionNumber = accessionNums == null ? new LinkedList<DBAccessionNumberPair>() : accessionNums;
			this.name = vals.get("name");
			this.rnaClass = vals.get("rnaClass");
			this.rnaSubClass = vals.get("rnaSubClass");
			this.sequence = vals.get("sequence");
			this.alignment = vals.get("alignment");
			this.structure = structures == null ? new HashMap<String, RnaAlgoPrediction>() : structures;
			this.duplicate = vals.get("duplicate") == null ? null : vals.get("duplicate").equals("1");
			this.inAlignment = vals.get("inAlignment") == null ? null : vals.get("inAlignment").equals("1");
			this.dense = getDouble(vals.get("dense"));
			this.denseComp = getDouble(vals.get("denseComp"));
			this.percIdentity = getDouble(vals.get("percIdentity"));
			this.alnseqid = getInteger(vals.get("alnseqid"));
			this.seqid = getInteger(vals.get("seqid"));
			this.filid = getInteger(vals.get("filid"));
			this.infoid = getInteger(vals.get("infoid"));
			this.deleted = new LinkedList<RnaAlgoPrediction>();
		} else
			throw new IllegalArgumentException();
	}
	
	@Override
	public String toString() {
		StringBuilder sb = new StringBuilder();
		sb.append(Display.box(this.name, "\t")).append("\n");
		sb.append("Accession Number: ").append(this.accessionNumber).append("\n");
		sb.append("RNA Class: ").append(this.rnaClass).append("\n");
		sb.append("RNA SubClass: ").append(this.rnaSubClass).append("\n");
		sb.append("Sequence: ").append(this.sequence).append("\n");
		sb.append("Alignment: ").append(this.alignment).append("\n");
		sb.append("Duplicate: ").append(this.duplicate).append("\n");
		sb.append("In Alignment: ").append(this.inAlignment).append("\n");
		sb.append("Dense: ").append(this.dense).append("\n");
		sb.append("Dense Comparison: ").append(this.denseComp).append("\n");
		sb.append("alnseqid: ").append(this.alnseqid).append("\n");
		sb.append("seqid: ").append(this.seqid).append("\n");
		sb.append("filid: ").append(this.filid).append("\n");
		sb.append("infoid: ").append(this.infoid).append("\n");
		sb.append(Display.underline("Structures:", "")).append("\n");
		for (RnaAlgoPrediction pred : structure.values())
			sb.append("\t").append(pred).append("\n");
		return sb.toString();
	}
	
	@Override
	public boolean equals(Object thatO) {
		if (thatO instanceof AlignedSequence){
			AlignedSequence that = (AlignedSequence) thatO;
			return this.getAccessionNumber().equals(that.getAccessionNumber()) && this.getName().equals(that.getName());
		}
		return false;
	}
	
	public static class ComparatorIdentity implements Comparator<AlignedSequence> {
		@Override
		public int compare(AlignedSequence seq1, AlignedSequence seq2) {
			if (seq1.percIdentity == null)
				return 1;
			if (seq2.percIdentity == null || seq1.percIdentity < seq2.percIdentity)
				return -1;
			if (seq1.percIdentity > seq2.percIdentity)
				return 1;
			return 0;
		}
	}
	
	public static class ComparatorDense implements Comparator<AlignedSequence> {
		@Override
		public int compare(AlignedSequence seq1, AlignedSequence seq2) {
			if (seq1.dense == null)
				return 1;
			if (seq2.dense == null || seq1.dense < seq2.dense)
				return -1;
			if (seq1.dense > seq2.dense)
				return 1;
			return 0;
		}
	}
	
	public static class ComparatorDenseComp implements Comparator<AlignedSequence> {
		@Override
		public int compare(AlignedSequence seq1, AlignedSequence seq2) {
			if (seq1.denseComp == null)
				return 1;
			if (seq2.denseComp == null || seq1.denseComp < seq2.denseComp)
				return -1;
			if (seq1.denseComp > seq2.denseComp)
				return 1;
			return 0;
		}
	}
}
